﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System;
using System.Collections.Generic;
using System.IO;
using BrainTechLLC.ThreadSafeObjects;

namespace BrainTechLLC
{
	public static class MockObjectRepository
	{
		private static string _saveFilename;

		public static bool MockRecordingEnabled { get; set; }
		public static bool MockReplayEnabled { get; set; }

		internal static ThreadSafeLookup<Type, MockObjectsForClass> MockObjectsByClass = new ThreadSafeLookup<Type, MockObjectsForClass>();

		public static int MockCount
		{
			get
			{
				int count = 0;

				MockObjectsByClass.AllItems.ForEach(
					(mockClass) => mockClass.LookupByMethodName.AllItems.ForEach(
							(mockMethods) => count += mockMethods.LookupByParameters.AllTerminatingItemsList.Count));

				return count;
			}
		}

		public static int AddMockObjectsFromFile(string pathAndFileName)
		{
			int count = 0;

			if (File.Exists(pathAndFileName))
			{
				byte[] bytes = File.ReadAllBytes(pathAndFileName);
				List<MockObjectsForClass> allMockClasses = bytes.DeserializeFromCompressedBinary() as List<MockObjectsForClass>;

				allMockClasses.ForEach(
					(mockClass) => mockClass.LookupByMethodName.AllItems.ForEach(
						(mockMethod) => mockMethod.LookupByParameters.AllTerminatingItemsList.ForEach(
							(kvpStored) =>
							{
								StoredParametersAndReturnValue stored = kvpStored.Value;
								stored.UnpackageMockFromStorage(true, true);
								mockClass.ClassType.RecordMockObjectReturnValue(mockMethod.MethodName, stored.Parameters.ToArray(), stored.ReturnValue);
								count++;
							})));	
			}

			return count;
		}		

		public static int SaveMockObjectsToFile(string pathAndFileName)
		{
			byte[] bytes = MockObjectsByClass.AllItems.SerializeToCompressedBinary();
			File.WriteAllBytes(pathAndFileName, bytes);
			return MockCount;
		}

		public static void EnableMockRecording(this Type t, bool enabled)
		{
			if (enabled)
				MockRecordingEnabled = true;

			MockObjectsForClass mocks = RetrieveMockObjectsForClass(t);
			mocks.MockRecordingEnabled = enabled;
		}

		internal static MockObjectsForClass RetrieveMockObjectsForClass(this Type t)
		{
			MockObjectsForClass result = MockObjectsByClass[t];

			if (result == null)
			{
				result = new MockObjectsForClass() { ClassType = t, MockRecordingEnabled = true };
				MockObjectsByClass.AddOrSet(t, result);
			}

			return result;
		}

		internal static StoredParametersAndReturnValue GetMockObject(this Type t, string methodName, params object[] parameters)
		{
			// Retrieve the structure that holds all recorded objects for class type t
			MockObjectsForClass mockObjects = RetrieveMockObjectsForClass(t);

			// Calculate a list of hash values that can be used to uniquely identify parameter values
			List<int> paramHash = GetParametersHash(parameters);

			// Retrieve the structure that holds all recorded objects for a specific method name
			MockObjectsForMethod methodMocks = mockObjects.GetMockObjectsForMethod(methodName);

			// Find a pre-recorded return value for this class type, method name, and parameter values
			StoredParametersAndReturnValue found = methodMocks.LookupByParameters.FindItem(paramHash.ToArray());

			// The return value may need to be unpacked from compressed binary storage
			if (found != null && found.IsPacked)
				found.UnpackageMockFromStorage(true, true);

			return found;
		}

		internal static StoredParametersAndReturnValue RecordMockObjectReturnValue(this Type t, string methodName, object[] parameters, object returnValue)
		{
			// Retrieve the structure that holds all recorded objects for class type t
			MockObjectsForClass mockObjects = RetrieveMockObjectsForClass(t);
			
			// Retrieve the structure that holds all recorded objects for a specific method name
			MockObjectsForMethod methodMocks = mockObjects.GetMockObjectsForMethod(methodName);

			// Calculate a list of hash values that can be used to uniquely identify parameter values
			List<int> paramHash = GetParametersHash(parameters);
			int[] paramsHashArray = paramHash.ToArray();

			// Find any existing stored parameters/return value associated with this combo of 
			// type, method name, and parameters.
			StoredParametersAndReturnValue found = methodMocks.LookupByParameters.FindItem(paramsHashArray);

			if (found == null)
				found = new StoredParametersAndReturnValue();
			else
				methodMocks.LookupByParameters.RemoveTerminatingItem(paramsHashArray);

			// Set up values in StoredParametersAndReturnValue
			found.ListOfParameterHash = paramHash;
			found.Parameters = new List<object>(parameters);
			found.ReturnValue = returnValue;
			found.IsPacked = false;

			// Add this instance of StoredParametersAndReturnValue to the lookup data structure
			methodMocks.LookupByParameters.AddTerminatingItem(found, paramsHashArray);

			return found;
		}

		internal static List<int> GetParametersHash(object[] parameters)
		{
			List<int> results = new List<int>();

			for (int n = 0; n < parameters.Length; n++)
			{
				int hash = GetHashForParameter(parameters[n]);
				results.Add(hash);
			}

			return results;
		}

		public static void EnableRecording(string saveFilename)
		{
			_saveFilename = saveFilename;

			MockRecordingEnabled = true;
			MockReplayEnabled = false;
		}

		public static void FinishRecording()
		{
			if (!string.IsNullOrEmpty(_saveFilename))
				SaveMockObjectsToFile(_saveFilename);
		}

		public static void EnablePlayback(string mockDataFileName)
		{
			if (!string.IsNullOrEmpty(mockDataFileName))
				AddMockObjectsFromFile(mockDataFileName);

			MockRecordingEnabled = false;
			MockReplayEnabled = true;
		}

		internal static int GetHashForParameter(object o)
		{
			if (o == null)
				return -99;

			int hash = 0;

			if (o.ForEachIfArray<object>(item => hash += GetHashForParameter(item)))
				return hash;

			return o.GetHashCode();
		}		
	}
}

